package com.jason.mvc.servlet;

import com.jason.mvc.annoation.*;
import com.jason.mvc.config.Handler;
import com.jason.mvc.config.ParameterMapping;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;


public class DispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classPaths = new ArrayList<>();

    private Map<String, Object> beanMap = new HashMap<>();

    private Map<String, Handler> handlerMap = new HashMap<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doProgress(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doProgress(req, resp);
    }


    private void doProgress(HttpServletRequest req, HttpServletResponse resp) {
        resp.setCharacterEncoding("utf-8");
        String requestUrl = req.getRequestURI();
        try {
            //获取参数
            Map<String, String[]> parameterMap = req.getParameterMap();

            Handler handler =handlerMap.get(requestUrl);
            if (handler == null) {
                resp.getWriter().println("404 not found");
                return;
            }

            //安全验证
            String userName = req.getParameter("username");
            Set<String> securityUser = handler.getSecurityUser();
            if (securityUser != null && securityUser.size() > 0 && !securityUser.contains(userName)) {
                resp.setCharacterEncoding("utf-8");
                resp.getWriter().println("没有权限访问");
                return;
            }

            Object [] args = new Object[handler.getParameterMappings().size()];



            List<ParameterMapping> parameterMappings = handler.getParameterMappings();
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getType().getSimpleName().equals("HttpServletRequest")) {
                    args[parameterMapping.getIndex()] = req;
                } else if (parameterMapping.getType().getSimpleName().equals("HttpServletResponse")) {
                    args[parameterMapping.getIndex()] = resp;
                } else {
                    args[parameterMapping.getIndex()] = StringUtils.join(parameterMap.get(parameterMapping.getName()), ",");
                }
            }
            handler.getMethod().invoke(handler.getObject(), args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        //加载配置文件
        this.loadConfig(config.getInitParameter("servletConfig"));

        //包扫描
        this.doScan();

        //初始化bean
        this.doInitBean();

        //依赖注入
        this.dependency();

        //handleMapping
        this.doHandlerMapping();
    }

    private void doHandlerMapping() {
        beanMap.entrySet().forEach(entry -> {
            Object object = entry.getValue();
            Class<?> classz = object.getClass();
            try {
                if (classz.isAnnotationPresent(RequestMapping.class)) {
                    //RequestMapping注解  handler映射关系
                    RequestMapping requestMapping = classz.getDeclaredAnnotation(RequestMapping.class);
                    String baseUrl = requestMapping.value();

                    //Security注解
                    String[] securitys = null;
                    if (classz.isAnnotationPresent(Security.class)) {
                        Security security = classz.getDeclaredAnnotation(Security.class);
                        securitys = security.value();
                    }


                    //所有的方法
                    Method[] methods = classz.getDeclaredMethods();
                    if (methods != null && methods.length > 0) {
                        for (Method method : methods) {
                            if (method.isAnnotationPresent(RequestMapping.class)) {
                                RequestMapping methodRequestMapping = method.getDeclaredAnnotation(RequestMapping.class);
                                String url = methodRequestMapping.value();

                                Handler handler = new Handler();
                                handler.setUrl(baseUrl + url);
                                handler.setMethod(method);
                                List<ParameterMapping> parameterMappings = new ArrayList<>();
                                Parameter[] parameters = method.getParameters();
                                if (parameters != null && parameters.length > 0) {
                                    for (int index = 0; index < parameters.length; index ++) {
                                        Parameter parameter = parameters[index];
                                        ParameterMapping parameterMapping = new ParameterMapping();
                                        parameterMapping.setName(parameter.getName());
                                        parameterMapping.setType(parameter.getType());
                                        parameterMapping.setIndex(index);
                                        parameterMappings.add(parameterMapping);
                                    }
                                }
                                handler.setObject(object);
                                handler.setParameterMappings(parameterMappings);

                                //方法上的Security注解
                                String [] methodSecuritys = null;
                                if (method.isAnnotationPresent(Security.class)) {
                                    methodSecuritys = method.getDeclaredAnnotation(Security.class).value();
                                }

                                Set<String> securityUser = new HashSet();
                                if (securitys != null)
                                    securityUser.addAll(Arrays.asList(securitys));

                                if (methodSecuritys != null)
                                    securityUser.addAll(Arrays.asList(methodSecuritys));
                                handler.setSecurityUser(securityUser);
                                handlerMap.put(baseUrl + url, handler);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

    }

    /**
     * 依赖注入
     */
    private void dependency() {
       beanMap.entrySet().forEach(entry -> {
           Object object = entry.getValue();
           Class classz = object.getClass();
           Field[] fields = classz.getDeclaredFields();
           if (fields != null && fields.length > 0) {
               for (Field field : fields) {
                   if (field.isAnnotationPresent(Autowired.class)) {
                       Autowired autowired = field.getAnnotation(Autowired.class);
                       String dependency = autowired.value();
                       if (dependency.isEmpty())
                           dependency = field.getType().getTypeName();
                       field.setAccessible(true);
                       try {
                           field.set(object, beanMap.get(dependency));
                       } catch (IllegalAccessException e) {
                           e.printStackTrace();
                       }
                   }
               }
           }
       });
    }

    /**
     * 初始化容器
     */
    private void doInitBean() {
        if (classPaths.size() == 0)
            return;
        classPaths.forEach(classPath -> {
            try {
                Class<?> classz = Class.forName(classPath);
                //接口、注解annotation类型  不做处理
                if (classz.isInterface() || classz.isAnnotation())
                    return;

                if (classz.isAnnotationPresent(Service.class)) {
                    //Service 注解
                    Object object = classz.newInstance();
                    Service service = classz.getDeclaredAnnotation(Service.class);
                    String key = service.value();
                    if (key.isEmpty())
                        key = toFirstLower(classz.getTypeName());
                    beanMap.put(key, object);

                    //如果service有接口
                    Class<?>[] interfaces = classz.getInterfaces();
                    if (interfaces != null && interfaces.length > 0) {
                        for (Class<?> anInterface : interfaces) {
                            beanMap.put(anInterface.getTypeName(), object);
                        }
                    }
                } else if (classz.isAnnotationPresent(Controller.class)) {
                    //Controller注解
                    Object object = classz.newInstance();
                    Controller controller = classz.getDeclaredAnnotation(Controller.class);
                    String key = controller.value();
                    if (key.isEmpty())
                        key = toFirstLower(classz.getTypeName());
                    beanMap.put(key, object);


                }


            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 包扫描
     */
    private void doScan() {
        String basePackage = (String) properties.get("servlet.scan.package");
        this.doScan(basePackage);

    }

    private void doScan(String basePackage) {
        String basePath = this.getClass().getResource("/").getPath();
        String path = basePath + basePackage.replace(".", "/");
        File directory = new File(path);
        File [] files = directory.listFiles();
        for (File file : files) {
            String fileName = file.getName();
            String classPath = basePackage + "." + fileName;
            if (file.isDirectory())
                //目录 递归调用继续扫描
                doScan(classPath);
            else if (fileName.endsWith("class"))
                classPaths.add(classPath.substring(0, classPath.length() - 6));
        }
    }

    /**
     * 加载配置文件
     */
    private void loadConfig(String path) {
        System.out.println(path);
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String toFirstLower(String str) {
        if (str == null || str.isEmpty())
            return "";
        char [] chars = str.toCharArray();
        if (chars[0] > 'A' && chars[0] < 'Z')
            chars[0] = (char) ((int)chars[0] - 32);
        return new String(chars);
    }

    public static void main(String[] args) {

    }
}
